$(document).ready(function(){

	var extendDeep = function( a, b ) {
			$.extend( true, a, b );
		};

	var target, source;
	
	// sample objects for testing
	var obj = function(x){this.value = x;};
	var one = new obj('one');
	var two = new obj('two');

	var returnTrue = function(){ return true; };
	var returnFalse = function(){ return false; };
		
	// utility function for resetting the above objects, in case a test modified them when it should not have
	var resetOneTwo = function() {
			one.value = 'one';
			two.value = 'two';
		};

	var testCases = function( merge, moduleName ) {
	
			module( moduleName );
			
			test('null + null = ignore',function(){
					target = {};			// .foo does not exist
					source = { foo: null };	// .foo exists, but is null
					merge( target, source );
					equals( target.hasOwnProperty('foo'), false, 'null property ignored' );
				});
				
			test('null + scalar = assign',function(){
					target = {};			// .foo does not exist
					source = { foo:'bar' };	// .foo is a scalar value
					merge( target, source );
					ok( target.hasOwnProperty('foo'), 'property exists' );
					equals( target.foo, 'bar', 'property has expected value' );
				});
				
			test('null + array = deep copy',function(){
					resetOneTwo();
					target = {};					// .foo does not exist
					source = { foo: [ one, two ] };	// .foo is an array of objects
					merge( target, source );
					ok( target.hasOwnProperty('foo'), 'property exists' );
					ok( $.isArray( target.foo ), 'is an array' );
					equals( target.foo[0].value, one.value, 'first element has right value' );
					equals( target.foo[1].value, two.value, 'first element has right value' );
					target.foo[0].value = 'one.changed';
					target.foo[1].value = 'two.changed';
					ok( target.foo[0].value != one.value, 'first element is a copy' );
					ok( target.foo[1].value != two.value, 'second element is a copy' );
				});
			
			test('null + object = deep copy',function(){
					resetOneTwo();
					target = {};			// .foo does not exist
					source = { foo: one };	// .foo is an object
					merge( target, source );
					ok( target.hasOwnProperty('foo'), 'property exists' );
					ok( target.foo instanceof obj, 'and is the right type of object' );
					equals( target.foo.value, 'one', 'and has the right value' );
					target.foo.value = 'bar';
					ok( one.value == 'one', 'property is a copy of the object, not a reference to' );
				});				 
		
			test('null + function = assign',function(){
					target = {};
					source = { foo: returnTrue };
					merge( target, source );
					ok( target.hasOwnProperty('foo'), 'property exists' );
					ok( $.isFunction( target.foo ), 'property is a function' );
					ok( target.foo === returnTrue, 'property is reference to same function' );
				});
			
			test('scalar + null = ignore',function(){ 
					target = { foo:'bar' };		// .foo exists and is a scalar (non-object) value
					source = { foo:null };		// .foo is null so should not modify the target
					merge( target, source );
					equals( target.foo, 'bar', 'source did not modify the target' );
				});
				
			test('scalar + scalar = assign',function(){
					target = { foo:'bar' };		// .foo exists and is scalar
					source = { foo:'rab' };		// .bar is a different value
					merge( target, source );
					equals( target.foo, 'rab', 'updated' );
				});
				
			test('scalar + array = promote to an array and append contents',function(){
					resetOneTwo();
					target = { foo:'bar' };			// .foo is a scalar
					source = { foo: [ one, two ] };	// .foo is an array of objects
					merge( target, source );
					ok( $.isArray( target.foo ), 'is an array' );
					equals( target.foo.length, 3, 'has the right number of elements in it' );
					equals( target.foo[0], 'bar', 'verify element 0' );
					equals( target.foo[1].value, one.value, 'verify element 1' );
					equals( target.foo[2].value, two.value, 'verify element 2' );
					target.foo[1].value = 'one.changed';
					target.foo[2].value = 'two.changed';
					ok( target.foo[1].value != one.value, 'first element is a copy' );
					ok( target.foo[2].value != two.value, 'second element is a copy' );
				});
		
			test('scalar + object = assign deep copy (lose scalar)',function(){
					resetOneTwo();
					target = { foo:'bar' };
					source = { foo: one };
					merge( target, source );
					ok( target.foo instanceof obj, 'converted to object' );
					target.foo.value = 'one.changed';
					ok( target.foo.value != one.value, 'object is a copy' );
				});
				
			test('scalar + function = assign (not copy)',function(){
					target = { foo: 'bar' };
					source = { foo: returnTrue };
					merge( target, source );
					ok( $.isFunction( target.foo ), 'property is a function' );
					ok( target.foo === returnTrue, 'property is reference to same function' );
				});

			test('array + null = ignore',function(){
					resetOneTwo();
					target = { foo: [ one, two ] };
					source = { foo: null };
					merge( target, source );
					ok( $.isArray( target.foo ), 'is an array' );
					equals( target.foo.length, 2, 'not modified by null value' );
					target.foo[0].value = 'one.changed';
					equals( one.value, 'one.changed', 'existing object in target is not being cloned' );
				});
			
			test('array + scalar = append',function(){
					resetOneTwo();
					target = { foo: [ one, two ] };
					source = { foo: 'bar' };
					merge( target, source );
					ok( $.isArray( target.foo ), 'is an array' );
					equals( target.foo.length, 3, 'array now has 3 elements' );
					equals( target.foo[2], 'bar', 'new element is correct value' );
				});

			test('array + array = append',function(){
					resetOneTwo();
					target = { foo: [ 'a', 'b' ] };
					source = { foo: [ one, two ] };
					merge( target, source );
					ok( $.isArray( target.foo ), 'is an array' );
					equals( target.foo.length, 4, 'array now has 4 elements' );
					ok( target.foo[2] instanceof obj, 'element 3 is an obj' );
					ok( target.foo[3] instanceof obj, 'element 4 is an obj' );
					target.foo[2].value = 'one.changed';
					target.foo[3].value = 'two.changed';
					ok( one.value == 'one', 'element 3 is a clone of one' );
					ok( two.value == 'two', 'element 4 is a clone of two' );
				});
				
			test('array + object = append copy',function(){
					resetOneTwo();
					target = { foo: [ 'a', 'b' ] };
					source = { foo: one };
					merge( target, source );
					ok( $.isArray( target.foo ), 'is an array' );
					equals( target.foo.length, 3, 'array now has 3 elements' );
					ok( target.foo[2] instanceof obj, 'element 3 is an obj' );
					target.foo[2].value = 'one.changed';
					ok( one.value == 'one', 'element 3 is a clone of one' );
				});
			
			test('array + function = append reference',function(){
					target = { foo: [ 'a', 'b' ] };
					source = { foo: returnTrue };
					merge( target, source );
					ok( $.isArray( target.foo ), 'is an array' );
					equals( target.foo.length, 3, 'array now has 3 elements' );
					ok( $.isFunction( target.foo[2] ), 'element 3 is a function' );
					ok( target.foo[2] === returnTrue, 'and is a reference to the same function' );
				});
				
			test('object + null = ignore',function(){
					resetOneTwo();
					target = { foo: one };
					source = { foo: null };
					merge( target, source );
					ok( target.foo.value == 'one', 'target unmodified' );
				});
				
			test('object + scalar = make new property',function(){	// debatable whether or not this is what it should do
					target = { foo: { one: 1 } };
					source = { foo: 'two' };
					merge( target, source );
					equals( target.foo.one, 1, '.one is preserved' );
					ok( target.foo.hasOwnProperty( 'two' ), '.two created' );
					ok( target.foo.two == null, '.two initialized to null' );
				});
				
			test('object + array = promote to array and concat',function(){
					resetOneTwo();
					target = { foo: one };
					source = { foo: [ two ] };
					merge( target, source );
					ok( $.isArray( target.foo ), '.foo promoted to array' );
					equals( target.foo.length, 2, '.foo has 2 elements' );
					equals( target.foo[0].value, 'one', 'first element is old value' );
					equals( target.foo[1].value, 'two', 'second element is incoming value' );
					target.foo[1].value = 'two.changed';
					equals( two.value, 'two', 'second element is a copy, not the original' );
				});
				
			test('object + object = merge with copies of contents',function(){
					var x = { 'two': 2 };
					target = { foo: { one: 1 } };
					source = { foo: x };
					merge( target, source );
					ok( target.foo.hasOwnProperty('one'), '.one retained' );
					ok( target.foo.hasOwnProperty('two'), '.two added' );
					target.foo.two = 3;
					ok( x.two == 2, '.two is a copy' );
				});
				
			test('object + function = replace the object with the function',function(){
					target = { foo: { bar: 'bar' } };
					source = { foo: returnTrue };
					merge( target, source );
					ok( $.isFunction( target.foo ), 'converted to function' );
					ok( target.foo === returnTrue, 'function was assigned, not cloned' );
					ok( target.foo(), 'is callable' );
					ok( ! target.foo.hasOwnProperty('bar'), '.bar is lost' );
					ok( ! returnTrue.hasOwnProperty('bar'), 'function was not modified in the process' );
				});
			
			test('function + null = ignore',function(){
					target = { foo: returnTrue };
					source = { foo: null };
					merge( target, source );
					ok( $.isFunction( target.foo ), 'property is a function' );
					ok( target.foo === returnTrue, 'property is reference to same function' );
				});
				
			test('function + scalar = replace function with scalar',function(){
					target = { foo: returnTrue };
					source = { foo: 'bar' };
					merge( target, source );
					equals( target.foo, 'bar', 'property value replaced' );
					ok( returnTrue(), 'make sure the function did not get clobbered' );
				});
			
			test('function + array = promote to array and concat',function(){
					target = { foo: returnTrue };
					source = { foo: [ one, two ] };
					merge( target, source );
					ok( $.isArray( target.foo ), 'promoted to array' );
					equals( target.foo.length, 3, 'has 3 elements in it' );
					ok( target.foo[0] === returnTrue, 'first element is old contents' );
					ok( target.foo[1] instanceof obj, 'second element' );
					equals( target.foo[1].value, 'one', '...value' ); 
					ok( target.foo[2] instanceof obj, 'third element' );
					equals( target.foo[2].value, 'two', '...value' );
					target.foo[1].value = 'one.changed';
					target.foo[2].value = 'two.changed';
					ok( one.value == 'one', 'one copied not referenced' );
					ok( two.value == 'two', 'two copied not referenced' );
				});
				
			test('function + object = extend function with object properties',function(){
					target = { foo: returnTrue };
					source = { foo: { bar: 'foo' } };
					merge( target, source );
					ok( $.isFunction( target.foo ), '.foo is still a function' );
					ok( target.foo(), '.foo is still callable' );
					ok( target.foo.hasOwnProperty('bar'), '.foo extended with .bar property' );
					equals( target.foo.bar, 'foo', '.bar has correct value' );
					
				});
				
			test('function + function = replace',function(){
					target = { foo: returnTrue };
					source = { foo: returnFalse };
					merge( target, source );
					ok( $.isFunction( target.foo ), 'still a function' );
					ok( target.foo === returnFalse, 'is correct function' );
					ok( ! target.foo(), 'returns correct value' ); 
				}); 
										
		};

	testCases( extendDeep,	'jQuery.extend(true,...)' );	// deep version of jQuery.extend
	//testCases( merge,		'custom' );	// my 'merge' function

});